/*
 * CCVisu is a tool for visual graph clustering
 * and general force-directed graph layout.
 * This file is part of CCVisu.
 *
 * Copyright (C) 2005-2012  Dirk Beyer
 *
 * CCVisu is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * CCVisu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with CCVisu; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please find the GNU Lesser General Public License in file
 * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt
 *
 * Dirk Beyer    (firstname.lastname@uni-passau.de)
 * University of Passau, Bavaria, Germany
 */
package org.sosy_lab.ccvisu.ui.controlpanel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.EventObject;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;

import org.sosy_lab.ccvisu.graph.GraphData;
import org.sosy_lab.ccvisu.graph.GraphVertex;
import org.sosy_lab.ccvisu.graph.GraphVertex.Shape;
import org.sosy_lab.ccvisu.graph.Group;
import org.sosy_lab.ccvisu.graph.NameVisibility;
import org.sosy_lab.ccvisu.graph.interfaces.GraphEventListener;
import org.sosy_lab.ccvisu.readers.ReaderData;
import org.sosy_lab.ccvisu.readers.ReaderWriterGroup;
import org.sosy_lab.ccvisu.ui.DialogEditGroup;
import org.sosy_lab.ccvisu.ui.helper.ColorComboBox;
import org.sosy_lab.ccvisu.ui.helper.ShapeComboBox;
import org.sosy_lab.ccvisu.writers.WriterDataLayoutDISP;
import org.sosy_lab.util.Colors;

/**
 * a GUI to manage the groups define new, remove, …
 */
@SuppressWarnings("serial")
public class ToolPanelGroups extends ControlPanel {

  private static final String GROUPS_FILE_EXT = ".grp";

  private final JButton           saveButton       = new JButton("Save...");
  private final JButton           loadButton       = new JButton("Load...");
  private final JButton           upButton         = new JButton("Up");
  private final JButton           downButton       = new JButton("Down");
  private final JButton           deleteButton     = new JButton("Delete");
  private final JButton           editButton       = new JButton("Edit");
  private final JButton           newButton        = new JButton("New");
  private final JButton           hideLabelsButton = new JButton("Hide labels");
  private final JButton           showLabelsButton = new JButton("Show labels");

  private JCheckBox               showGroupCheckBox;
  private JCheckBox               showGraphicInfoCheckBox;

  private JLabel                  chosenGroups;

  private ColorComboBox           colorComboBox;
  private ShapeComboBox           shapeComboBox;

  private JTabbedPane             tabbedPane;
  private JPanel                  groupDetailsPanel;
  private JPanel                  groupMeasuresPanel;

  private JList<Group>            groupsList;
  private DefaultListModel<Group> groupsListModel;

  private final GraphData graph;


  public ToolPanelGroups(WriterDataLayoutDISP writer, GraphData graph) {
    this.graph = graph;

    initComponents();
    assignListeners();
    refillGroupList();
    enableAvailableControls();
  }

  private void initComponents() {
    this.setLayout(new BorderLayout());

    JPanel groupsPanel = new JPanel();
    groupsPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
    groupsPanel.setLayout(new BorderLayout(0, 0));
    add(groupsPanel, BorderLayout.WEST);

    groupsListModel = new DefaultListModel<Group>();
    groupsList = new JList<>(groupsListModel);
    groupsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    groupsList.setBorder(null);
    groupsPanel.add(new JScrollPane(groupsList));

    JPanel groupEditCtrlPanel = new JPanel();
    groupsPanel.add(groupEditCtrlPanel, BorderLayout.SOUTH);
    groupEditCtrlPanel.setLayout(new BorderLayout(0, 0));

    JToolBar editToolBar = new JToolBar();
    editToolBar.setFloatable(false);
    groupEditCtrlPanel.add(editToolBar);

    editToolBar.add(newButton);
    editToolBar.add(editButton);
    editToolBar.add(deleteButton);

    JToolBar sortToolBar = new JToolBar();
    sortToolBar.setFloatable(false);
    groupEditCtrlPanel.add(sortToolBar, BorderLayout.EAST);

    sortToolBar.add(upButton);
    sortToolBar.add(downButton);

    upButton.setHorizontalAlignment(SwingConstants.RIGHT);
    downButton.setHorizontalAlignment(SwingConstants.RIGHT);

    JToolBar storeToolBar = new JToolBar();
    storeToolBar.setFloatable(false);
    groupsPanel.add(storeToolBar, BorderLayout.NORTH);

    storeToolBar.add(loadButton);
    storeToolBar.add(saveButton);

    // create tabbed pane and its tabs and add the pane to this, i.e., the
    // control panel
    tabbedPane = new JTabbedPane(SwingConstants.TOP);
    groupDetailsPanel = createTabGroupPanel(tabbedPane);
    groupMeasuresPanel = createTabMeasuresPanel(tabbedPane);
    tabbedPane.addTab("Group(s)", null, groupDetailsPanel, null);
    tabbedPane.addTab("Measures", null, groupMeasuresPanel, null);

    createTabMeasuresPanel(tabbedPane);
    add(tabbedPane, BorderLayout.CENTER);
  }

  private void refillGroupList() {
    groupsList.setEnabled(false);

    Group selectedGroup = groupsList.getSelectedValue();

    groupsListModel.clear();
    for (Group g : graph.getGroups()) {
      groupsListModel.addElement(g);
    }

    if (selectedGroup != null) {
      groupsList.setSelectedValue(selectedGroup, true);
    }

    groupsList.setEnabled(true);
  }

  private JPanel createTabGroupPanel(JTabbedPane tabbedPane) {
    JPanel result = new JPanel(new BorderLayout(5, 5));
    JPanel inner = new JPanel(new GridBagLayout());

    chosenGroups = new JLabel("");
    chosenGroups.setFont(chosenGroups.getFont().deriveFont(Font.BOLD));
    addOptionControls(inner, "", chosenGroups);
    addOptionControls(inner, "", new JLabel(""));

    showGroupCheckBox = new JCheckBox("Show group");
    addOptionControls(inner, "", showGroupCheckBox);

    showGraphicInfoCheckBox = new JCheckBox("Graphic informations");
    addOptionControls(inner, "", showGraphicInfoCheckBox);

    shapeComboBox = new ShapeComboBox(Shape.DISC);
    addOptionControls(inner, "Shape", shapeComboBox);

    colorComboBox = new ColorComboBox(Colors.RED.get());
    addOptionControls(inner, "Color", colorComboBox);

    addOptionControls(inner, "", showLabelsButton);
    addOptionControls(inner, "", hideLabelsButton);

    result.add(inner, BorderLayout.NORTH);
    return result;
  }

  private JPanel createTabMeasuresPanel(JTabbedPane tabbedPane) {
    JPanel result = new JPanel();
    result.setLayout(new BorderLayout());

    return result;
  }

  protected void assignListeners() {

    graph.addOnGroupChangeListener(new GraphEventListener() {
      @Override
      public void onGraphChangedEvent(EventObject event) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            if (groupsList.isEnabled()) {
              refillGroupList();
            }
          }
        });
      }
    });

    groupsList.addListSelectionListener(new ListSelectionListener() {
      @Override
      public void valueChanged(ListSelectionEvent event) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            enableAvailableControls();
            if (groupsList.isEnabled()) {
              updateGroupInfos();
            }
          }
        });
      }
    });

    // Adds ActionListener for action event "Save button pressed".
    // Opens a dialog for choosing the file to which the group definition is saved.
    saveButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        // Create a fileDialog and save the info in the selected file
        JFileChooser fileDialog = new JFileChooser(".");
        fileDialog.setFileFilter(ReaderData.mkExtensionFileFilter(GROUPS_FILE_EXT, "Group Files"));
        int outcome = fileDialog.showSaveDialog((Frame) null);

        if (outcome == JFileChooser.APPROVE_OPTION) {
          assert (fileDialog.getCurrentDirectory() != null);
          assert (fileDialog.getSelectedFile() != null);

          String fileName = fileDialog.getCurrentDirectory().toString()
              + File.separator
              + fileDialog.getSelectedFile().getName();
          if (!fileName.endsWith(GROUPS_FILE_EXT)) {
            fileName += GROUPS_FILE_EXT;
          }

          try {
            PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
            ReaderWriterGroup.write(out, graph);
            System.err.println("Wrote groups informations to output '" + fileName + "'.");
            out.close();
          } catch (IOException e) {
            System.err.println("error while writing (GroupManager.saveClt):");
            e.printStackTrace();
          }

        }
      }
    });

    // Adds ActionListener for action event ''Load button pressed''.
    // Opens a dialog for choosing the file from which the group definition is loaded.
    loadButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent evt) {
        // Create a fileDialog and try to load the info from the selected file
        JFileChooser fileDialog = new JFileChooser(".");
        fileDialog.setFileFilter(ReaderData.mkExtensionFileFilter(GROUPS_FILE_EXT, "Group Files"));
        int outcome = fileDialog.showOpenDialog((Frame) null);

        if (outcome == JFileChooser.APPROVE_OPTION) {
          assert (fileDialog.getCurrentDirectory() != null);
          assert (fileDialog.getSelectedFile() != null);

          String fileName = fileDialog.getCurrentDirectory().toString()
              + File.separator
              + fileDialog.getSelectedFile().getName();
          // Read group definition from file.
          BufferedReader fileReader = null;
          try {
            fileReader = new BufferedReader(new FileReader(fileName));
            ReaderWriterGroup.read(fileReader, graph);
            //  Close the input file.
            fileReader.close();
          } catch (Exception e) {
            System.err.println("Exception while reading from file '" + fileName + "'.");
            System.err.println(e);
          }

        }
      }
    });

    abstract class SelectedGroupsModifier {
      public final void run() {
        if (groupDetailsPanel.isEnabled()) {
          for (Group group : groupsList.getSelectedValuesList()) {
            modifyGroup(group);
          }
          graph.notifyAboutGroupsChange(null);
        }
      }

      public abstract void modifyGroup(Group group);
    }

    colorComboBox.addItemListener(new ItemListener() {
      @Override
      public void itemStateChanged(ItemEvent e) {
        (new SelectedGroupsModifier() {
          @Override
          public void modifyGroup(Group group) {
            Color color = colorComboBox.getSelectedColor();
            assert (color != null);
            group.setColor(color);
            shapeComboBox.setColor(color);
          }
        }).run();
      }
    });

    shapeComboBox.addItemListener(new ItemListener() {
      @Override
      public void itemStateChanged(ItemEvent e) {
        (new SelectedGroupsModifier() {
          @Override
          public void modifyGroup(Group group) {
            Shape shape = shapeComboBox.getSelectedShape();
            assert (shape != null);
            group.setShape(shape);
          }
        }).run();
      }
    });

    showGroupCheckBox.addItemListener(new ItemListener() {
      @Override
      public void itemStateChanged(ItemEvent e) {
        (new SelectedGroupsModifier() {
          @Override
          public void modifyGroup(Group group) {
            group.setVisible(showGroupCheckBox.isSelected());
        }
        }).run();
      }
    });

    showGraphicInfoCheckBox.addItemListener(new ItemListener() {
      @Override
      public void itemStateChanged(ItemEvent e) {
        (new SelectedGroupsModifier() {
          @Override
          public void modifyGroup(Group group) {
            group.setDrawInfo(showGraphicInfoCheckBox.isSelected());
          }
          }).run();
        }
    });

    showLabelsButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        (new SelectedGroupsModifier() {
          @Override
          public void modifyGroup(Group group) {
            for (GraphVertex graphVertex : group.getNodes()) {
              graphVertex.setShowName(NameVisibility.Priority.GROUPS, true);
            }
          }
          }).run();
        }
    });

    hideLabelsButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        (new SelectedGroupsModifier() {
          @Override
          public void modifyGroup(Group group) {
            for (GraphVertex graphVertex : group.getNodes()) {
              graphVertex.setShowName(NameVisibility.Priority.GROUPS, false);
            }
          }
          }).run();
        }
    });

    // call the method in charge of creating a new group
    newButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        String newGroupName = JOptionPane.showInputDialog(null, "Enter a name",
            "Name of the new group", JOptionPane.QUESTION_MESSAGE);

        if (newGroupName != null) {
          if (graph.getGroup(newGroupName) == null) {
            graph.addGroup(new Group(newGroupName, graph));
          }
        }
      }
    });

    // open a dialog to edit the nodes of the selected group
    editButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        Group group = groupsList.getSelectedValue();
        if (group != null) {
          DialogEditGroup dialog = new DialogEditGroup(graph, group);
          dialog.setModal(true);
          dialog.setVisible(true);
        }
      }
    });

    // remove the selected group
    deleteButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        Group selectedGroup = groupsList.getSelectedValue();
        if (selectedGroup != null) {
          graph.removeGroup(selectedGroup);
        }
      }
    });

    // put the selected group one rank higher in the rendering list
    upButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        Group group = groupsList.getSelectedValue();
        if (group != null) {
          groupsList.setEnabled(false);
          graph.moveGroupUp(group);
          groupsList.setEnabled(true);
        }
      }
    });

    // put the selected group one rank lower in the rendering list
    downButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        Group group = groupsList.getSelectedValue();
        if (group != null) {
          groupsList.setEnabled(false);
          graph.moveGroupDown(group);
          groupsList.setEnabled(true);
        }
      }
    });

  }

  /**
   * refresh the information about the selected group
   */
  protected void updateGroupInfos() {
    Group group = groupsList.getSelectedValue();
    if (group == null) {
      return;
    }

    groupDetailsPanel.setEnabled(false);
    try {
      String chosenGroupDesc = group.getName();
      chosenGroups.setText(chosenGroupDesc);

      // refresh color
      String colorString = Colors.toString(group.getColor());
      if (colorString != null) {
        Colors color = Colors.valueOfUpper(colorString);
        colorComboBox.setSelectedColor(color);
      }

      showGraphicInfoCheckBox.setSelected(group.isDrawInfo());
      showGroupCheckBox.setSelected(group.isVisible());

      // refresh shape
      shapeComboBox.setColor(colorComboBox.getSelectedColor());
      shapeComboBox.setSelectedItem(group.getShape());
      shapeComboBox.validate();

      renderGroupMeasures(group);
    } finally {
      groupDetailsPanel.setEnabled(true);
    }
  }

  protected void renderGroupMeasures(Group group) {
    final Object[][] data = new Object[][]{
      {"Average radius", Float.toString(group.getAverageRadius())},
      {"Number of nodes", Integer.toString(group.getNodes().size())},
      {"Barycenter X", Float.toString(group.getX())},
      {"Barycenter Y", Float.toString(group.getY())},
      {"Barycenter Z", Float.toString(group.getZ())}
      };
    final Object[] cols = new Object[] {"Measure", "Value"};

    ReadOnlyTableModel measuresModel = new ReadOnlyTableModel(data, cols);
    JTable measuresTable = new JTable(measuresModel);
    measuresTable.setPreferredScrollableViewportSize(new Dimension(200, 200));

    TableColumnModel colModel = measuresTable.getColumnModel();
    colModel.getColumn(0).setWidth(150);
    colModel.getColumn(0).setResizable(false);
    colModel.getColumn(1).setWidth(100);
    colModel.getColumn(1).setResizable(false);

    JScrollPane scrollPane = new JScrollPane();
    scrollPane.setViewportView(measuresTable);

    groupMeasuresPanel.removeAll();
    groupMeasuresPanel.add(scrollPane, BorderLayout.CENTER);
    groupMeasuresPanel.validate();
  }

  private void enableAvailableControls() {
    final int selectedGroups = groupsList.getSelectedIndices().length;
    final int availGroups = groupsListModel.getSize();
    final boolean graphHasNodes = graph.getVertices().size() > 0;
    final boolean groupsSelected = selectedGroups > 0;

    downButton.setEnabled(availGroups > 1 && selectedGroups == 1 && groupsList.getSelectedIndex() != availGroups-1);
    upButton.setEnabled(availGroups > 1 && selectedGroups == 1  && groupsList.getSelectedIndex() != 0);

    editButton.setEnabled(selectedGroups == 1);
    deleteButton.setEnabled(selectedGroups > 0);
    newButton.setEnabled(graphHasNodes);

    saveButton.setEnabled(availGroups > 0);

    tabbedPane.setEnabledAt(0, groupsSelected);
    tabbedPane.setEnabledAt(1, selectedGroups == 1);

    hideLabelsButton.setEnabled(groupsSelected);
    showLabelsButton.setEnabled(groupsSelected);
    shapeComboBox.setEnabled(groupsSelected);
    colorComboBox.setEnabled(groupsSelected);
    showGraphicInfoCheckBox.setEnabled(groupsSelected);
    showGroupCheckBox.setEnabled(groupsSelected);
  }

  private static class ReadOnlyTableModel extends DefaultTableModel {

    public ReadOnlyTableModel(Object[][] data, Object[] cols) {
      super(data, cols);
    }

    @Override
    public boolean isCellEditable(int row, int column) {
      return false;
    }
  }

}
